﻿using Devil;
using System;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{
    public interface IAutoCreateNodePort
    {
        FilterDelegate<SearchTreeEntry> AutoCreationFilter { get; set; }
    }

    public interface ISingleConnectionBetweenNodes
    {
        int Id { get; set; }
        bool IsSingleConnection { get; set; }
    }

    public class GraphPort<TEdge> : Port, ISingleConnectionBetweenNodes, IAutoCreateNodePort where TEdge : Edge, new()
    {
        public int Id { get; set; }
        public bool IsSingleConnection { get; set; }
        public bool SyncOutputColor { get; set; }

        public FilterDelegate<SearchTreeEntry> AutoCreationFilter { get; set; }

        public GraphPort(Orientation orientation, Direction direction, Port.Capacity capacity, Type type) : base(orientation, direction, capacity, type)
        {
            var listener = new EdgeConnectorListener();
            m_EdgeConnector = new EdgeConnector<TEdge>(listener);
            this.AddManipulator(m_EdgeConnector);
        }

        public override void Connect(Edge edge)
        {
            base.Connect(edge);
            var port = edge.input as GraphPort<TEdge>;
            if (port != null && port.SyncOutputColor && edge.output != null)
                port.portColor = edge.output.portColor;
        }
    }

    public class AutoCreateFilter
    {
        public readonly Port autoConnectPort;
        public readonly FilterDelegate<SearchTreeEntry> nodeFilter;

        public AutoCreateFilter(Port autoConnect, FilterDelegate<SearchTreeEntry> filter)
        {
            this.autoConnectPort = autoConnect;
            this.nodeFilter = filter;
        }
    }

    public interface IGraphViewAutoConnector
    {
        void CreateNode(AutoCreateFilter filter, Vector2 pos);
    }

    internal class EdgeConnectorListener : IEdgeConnectorListener
    {
        private GraphViewChange m_GraphViewChange;

        private List<Edge> m_EdgesToCreate;

        private List<GraphElement> m_EdgesToDelete;

        public EdgeConnectorListener()
        {
            m_EdgesToCreate = new List<Edge>();
            m_EdgesToDelete = new List<GraphElement>();
            m_GraphViewChange.edgesToCreate = m_EdgesToCreate;
        }

        public void OnDropOutsidePort(Edge edge, Vector2 position)
        {
            var port = edge.input == null ? edge.output as IAutoCreateNodePort : edge.input as IAutoCreateNodePort;
            if (port == null || port.AutoCreationFilter == null)
                return;
            var graph = edge.GetFirstAncestorOfType<GraphView>() as IGraphViewAutoConnector;
            graph?.CreateNode(new AutoCreateFilter((Port)port, port.AutoCreationFilter), position);
        }

        public void OnDrop(GraphView graphView, Edge edge)
        {
            m_EdgesToCreate.Clear();
            m_EdgesToCreate.Add(edge);
            m_EdgesToDelete.Clear();

            
            if (edge.input.capacity == Port.Capacity.Single)
            {
                foreach (Edge connection in edge.input.connections)
                {
                    if (connection != edge && !m_EdgesToDelete.Contains(connection))
                    {
                        m_EdgesToDelete.Add(connection);
                    }
                }
            }

            if (edge.output.capacity == Port.Capacity.Single)
            {
                foreach (Edge connection2 in edge.output.connections)
                {
                    if (connection2 != edge && !m_EdgesToDelete.Contains(connection2))
                    {
                        m_EdgesToDelete.Add(connection2);
                    }
                }
            }

            var single1 = edge.input is ISingleConnectionBetweenNodes sig1 && sig1.IsSingleConnection;
            var single2 = edge.output is ISingleConnectionBetweenNodes sig2 && sig2.IsSingleConnection;
            if (single1)
            {
                var dest = edge.output.node;
                foreach (var conn in edge.input.connections)
                {
                    if (conn != edge && conn.output.node == dest && !m_EdgesToDelete.Contains(conn))
                        m_EdgesToDelete.Add(conn);
                }
            }
            if (single2)
            {
                var dest = edge.input.node;
                foreach (var conn in edge.output.connections)
                {
                    if (conn != edge && conn.input.node == dest && !m_EdgesToDelete.Contains(conn))
                        m_EdgesToDelete.Add(conn);
                }
            }

            if (m_EdgesToDelete.Count > 0)
            {
                graphView.DeleteElements(m_EdgesToDelete);
            }

            List<Edge> edgesToCreate = m_EdgesToCreate;
            if (graphView.graphViewChanged != null)
            {
                edgesToCreate = graphView.graphViewChanged(m_GraphViewChange).edgesToCreate;
            }

            foreach (Edge item in edgesToCreate)
            {
                graphView.AddElement(item);
                edge.input.Connect(item);
                edge.output.Connect(item);
            }
        }
    }
}
